Overview: While blockchain enhances security, transparency, and efficiency, various attacks still threaten its applications in non-financial sectors. Security vulnerabilities, if left unaddressed, can lead to data manipulation, unauthorized access, and financial loss.
Potential Issues:
Solution:
Code Sample: Updated Supply Chain Tracking Contract with Security Enhancements
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SupplyChain is ReentrancyGuard {
address public admin;
mapping(address => bool) public authorizedEntities;
mapping(address => uint) public roles; // 0: ADMIN, 1: AUTHORIZER, 2: MANUFACTURER
struct Product {
string name;
address manufacturer;
bool isVerified;
}
mapping(uint => Product) public products;
constructor() {
admin = msg.sender;
roles[msg.sender] = 0; // Assign admin role to deployer
}
modifier onlyAdmin() {
require(roles[msg.sender] == 0, "Only admin can perform this action");
_;
}
modifier onlyAuthorized() {
require(authorizedEntities[msg.sender] || roles[msg.sender] == 0, "Not authorized");
_;
}
modifier onlyManufacturer(uint productId) {
require(msg.sender == products[productId].manufacturer || roles[msg.sender] == 0, "Only manufacturer can verify");
_;
}
function authorizeEntity(address entity, uint role) public onlyAdmin {
authorizedEntities[entity] = true;
roles[entity] = role;
}
function addProduct(uint productId, string memory name) public onlyAuthorized nonReentrant {
products[productId] = Product(name, msg.sender, false);
}
function verifyProduct(uint productId) public onlyManufacturer(productId) nonReentrant {
products[productId].isVerified = true;
}
}
Prevention Insights:
Potential Issues:
Solution:
Code Sample: Enhanced Medical Record Storage
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract SecureMedicalRecords is AccessControl, ReentrancyGuard {
using ECDSA for bytes32;
// Roles
bytes32 public constant DOCTOR_ROLE = keccak256("DOCTOR_ROLE");
bytes32 public constant PATIENT_ROLE = keccak256("PATIENT_ROLE");
struct Record {
bytes32 encryptedDiagnosis; // Encrypted with patient’s private key for privacy
bytes32 encryptedTreatment;
uint256 timestamp;
}
// Mapping of patient addresses to medical records
mapping(address => Record[]) private records;
// Event logs for monitoring
event RecordAdded(address indexed patient, uint256 timestamp);
constructor() {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender); // Deployer as admin
}
/**
* @notice Adds a new medical record for the patient.
* @dev Only the assigned doctor can add records for a patient.
* @param patient The address of the patient.
* @param encryptedDiagnosis Hashed diagnosis encrypted with patient’s public key.
* @param encryptedTreatment Hashed treatment encrypted with patient’s public key.
*/
function addRecord(
address patient,
bytes32 encryptedDiagnosis,
bytes32 encryptedTreatment
) public nonReentrant onlyRole(DOCTOR_ROLE) {
require(hasRole(PATIENT_ROLE, patient), "Not a registered patient");
// Save the record securely
records[patient].push(
Record(encryptedDiagnosis, encryptedTreatment, block.timestamp)
);
emit RecordAdded(patient, block.timestamp);
}
/**
* @notice Allows a patient to view their own records.
* @dev Only the patient can access their records, not doctors.
* @return Array of encrypted medical records.
*/
function viewRecords() external view onlyRole(PATIENT_ROLE) returns (Record[] memory) {
return records[msg.sender];
}
/**
* @notice Grants the doctor role to an address.
* @dev Only admins can add doctors.
* @param doctor The address to be granted the doctor role.
*/
function addDoctor(address doctor) public onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(DOCTOR_ROLE, doctor);
}
/**
* @notice Registers a new patient.
* @dev Only admins can register patients.
* @param patient The address of the patient.
*/
function registerPatient(address patient) public onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(PATIENT_ROLE, patient);
}
}
Role-Based Access Control:
DOCTOR_ROLE) can add records.PATIENT_ROLE) can view their own records, not other patients or doctors.Prevention Insights:
Potential Issues:
Solution:
Code Sample: Improved Voting System with Commit-Reveal Scheme
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SecureVoting {
struct Voter {
bytes32 commitment;
bool revealed;
uint8 vote;
}
mapping(address => Voter) private voters;
uint256 public yesVotes;
uint256 public noVotes;
uint256 public commitEndTime;
uint256 public revealEndTime;
modifier onlyDuringCommitPhase() {
require(block.timestamp < commitEndTime, "Commit phase has ended");
_;
}
modifier onlyDuringRevealPhase() {
require(block.timestamp >= commitEndTime && block.timestamp < revealEndTime, "Reveal phase not active");
_;
}
function commitVote(bytes32 _commitment) external onlyDuringCommitPhase {
require(voters[msg.sender].commitment == bytes32(0), "Already committed");
voters[msg.sender].commitment = _commitment;
}
function revealVote(uint8 _vote, string calldata _salt) external onlyDuringRevealPhase {
Voter storage voter = voters[msg.sender];
require(!voter.revealed, "Already revealed");
require(_vote == 1 || _vote == 0, "Vote must be 0 or 1");
bytes32 checkCommitment = keccak256(abi.encodePacked(_vote, _salt));
require(checkCommitment == voter.commitment, "Commitment mismatch");
voter.revealed = true;
if (_vote == 1) yesVotes++;
else noVotes++;
}
function getResults() public view returns (string memory) {
if (yesVotes > noVotes) return "Yes Wins";
else if (noVotes > yesVotes) return "No Wins";
else return "Tie";
}
}
Prevention Insights:
Potential Issues:
Solution:
Code Sample: Secure Lending Contract with Reentrancy Prevention
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureLending is ReentrancyGuard {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint amount) public nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
Prevention Insights:
Problem: Unauthorized access allows attackers to view, modify, or delete data without permission, which can expose sensitive information or disrupt services.
Solution: Implement Role-Based Access Control (RBAC). Assign roles like DOCTOR_ROLE and PATIENT_ROLE to restrict access to certain functions.
Code Example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/AccessControl.sol";
contract MedicalRecords is AccessControl {
bytes32 public constant DOCTOR_ROLE = keccak256("DOCTOR_ROLE");
bytes32 public constant PATIENT_ROLE = keccak256("PATIENT_ROLE");
struct Record {
string diagnosis;
string treatment;
uint256 timestamp;
}
mapping(address => Record[]) private records;
constructor() {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function addDoctor(address doctor) public onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(DOCTOR_ROLE, doctor);
}
function addPatient(address patient) public onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(PATIENT_ROLE, patient);
}
function addRecord(address patient, string memory diagnosis, string memory treatment) public onlyRole(DOCTOR_ROLE) {
records[patient].push(Record(diagnosis, treatment, block.timestamp));
}
function viewRecords() public view onlyRole(PATIENT_ROLE) returns (Record[] memory) {
return records[msg.sender];
}
}
In this contract, only users with the DOCTOR_ROLE can add medical records, and only those with the PATIENT_ROLE can view their own records.
Problem: A reentrancy attack happens when a function calls another contract which then calls back into the first contract before the initial execution is completed. This can drain funds or manipulate data.
Solution: Use the nonReentrant modifier to prevent functions from being called recursively.
Code Example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureBank is ReentrancyGuard {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) public nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
Here, the nonReentrant modifier prevents reentrancy attacks, as it disallows re-entry into the withdraw function until the current execution is completed.
Problem: Unvalidated inputs can allow for data corruption, injection attacks, and other unintended behaviors.
Solution: Implement checks for each input to ensure they meet expected parameters (e.g., non-empty strings, within numerical ranges).
Code Example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract InputValidatedSystem {
struct User {
string name;
uint8 age;
}
mapping(address => User) public users;
function registerUser(string memory name, uint8 age) public {
require(bytes(name).length > 0, "Name cannot be empty");
require(age > 0 && age < 120, "Invalid age range");
users[msg.sender] = User(name, age);
}
}
This example ensures the name is not empty and that age is within a reasonable range before adding the user to the system.
Problem: In a Sybil attack, the attacker creates multiple fake identities to gain control or manipulate consensus.
Solution: Use Proof of Identity by requiring verified identity data, or stake-based requirements (e.g., requiring deposits or reputation).
Code Example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract StakeBasedVoting {
struct Voter {
uint256 stake;
bool hasVoted;
uint8 vote;
uint256 lockTimestamp;
}
mapping(address => Voter) public voters;
uint256 public yesVotes;
uint256 public noVotes;
uint256 public constant MINIMUM_STAKE = 1 ether; // Require 1 ETH stake to vote
uint256 public constant WITHDRAW_DELAY = 1 days; // Lock funds for 24 hours after voting ends
uint256 public votingEndTime;
event VoteCasted(address indexed voter, uint8 vote);
event StakeWithdrawn(address indexed voter, uint256 amount);
modifier onlyDuringVoting() {
require(block.timestamp < votingEndTime, "Voting has ended");
_;
}
modifier onlyAfterVoting() {
require(block.timestamp >= votingEndTime, "Voting is still ongoing");
_;
}
constructor(uint256 _votingDuration) {
votingEndTime = block.timestamp + _votingDuration;
}
function vote(uint8 _vote) external payable onlyDuringVoting {
require(_vote == 0 || _vote == 1, "Invalid vote"); // 0 = No, 1 = Yes
require(msg.value >= MINIMUM_STAKE, "Minimum stake required");
require(!voters[msg.sender].hasVoted, "Already voted");
voters[msg.sender] = Voter({
stake: msg.value,
hasVoted: true,
vote: _vote,
lockTimestamp: block.timestamp + WITHDRAW_DELAY
});
if (_vote == 1) {
yesVotes++;
} else {
noVotes++;
}
emit VoteCasted(msg.sender, _vote);
}
function withdrawStake() external onlyAfterVoting {
Voter storage voter = voters[msg.sender];
require(voter.hasVoted, "No stake to withdraw");
require(block.timestamp >= voter.lockTimestamp, "Stake locked");
uint256 amount = voter.stake;
voter.stake = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
emit StakeWithdrawn(msg.sender, amount);
}
function getResult() external view returns (string memory) {
require(block.timestamp >= votingEndTime, "Voting is still ongoing");
if (yesVotes > noVotes) {
return "Yes wins";
} else if (noVotes > yesVotes) {
return "No wins";
} else {
return "It's a tie";
}
}
}
This example requires voters to deposit funds proportional to their weight, making it costly for attackers to create fake identities.
Problem: Replay attacks involve resending a previously valid transaction to perform unintended actions.
Solution: Use nonces to uniquely identify each transaction and ensure that repeated transactions are invalid.
Code Example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SecureNonce {
mapping(address => uint256) private nonces;
function getSecureNonce(address user) public view returns (uint256) {
// Generate a secure nonce using keccak256 with a mix of variables
return uint256(keccak256(abi.encodePacked(user, nonces[user], block.timestamp)));
}
function executeTransaction(uint256 providedNonce, uint256 amount) public {
uint256 expectedNonce = getSecureNonce(msg.sender);
require(providedNonce == expectedNonce, "Invalid nonce provided");
// Increment nonce for user to ensure each transaction is unique
nonces[msg.sender]++;
// Further transaction logic
}
}
The nonce is a keccak256 hash based on the user’s address, their current nonce count, and the block’s timestamp, creating a more complex and unique identifier.
Problem: Flash loan attacks exploit quick, uncollateralized loans to manipulate prices or other systems within a single transaction.
Solution: Use price oracles with time-weighted averages (TWAP) to prevent price manipulation within a single transaction.
Code Example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract TWAPPriceFeed {
AggregatorV3Interface internal priceFeed;
uint256[] private historicalPrices;
uint256 constant INTERVAL = 10 minutes;
uint256 constant DURATION = 1 hours;
constructor(address priceFeedAddress) {
priceFeed = AggregatorV3Interface(priceFeedAddress);
}
function updatePrice() public {
require(block.timestamp % INTERVAL == 0, "Updates only every 10 minutes");
(, int price,,,) = priceFeed.latestRoundData();
historicalPrices.push(uint256(price));
if (historicalPrices.length > DURATION / INTERVAL) {
// Keep only the latest prices within the duration window
historicalPrices.pop();
}
}
function getTWAP() public view returns (uint256) {
require(historicalPrices.length > 0, "No price data available");
uint256 sumPrices = 0;
for (uint256 i = 0; i < historicalPrices.length; i++) {
sumPrices += historicalPrices[i];
}
return sumPrices / historicalPrices.length;
}
}
A TWAP oracle mitigates flash loan manipulation by taking average prices over a period, which limits the effects of momentary spikes or dips.